package com.szwistar.common.comm;

import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.session.DefaultIoSessionDataStructureFactory;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.core.session.IoSessionAttributeMap;
import org.apache.mina.transport.socket.nio.NioDatagramAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 通信服务器端基类
 * <T> 表示服务器接收的客户端连接类型
 */
public class ServerBase<T extends CommEndpoint>
	extends DefaultIoSessionDataStructureFactory
	implements IoHandler
{
	protected static final Logger LOG = LoggerFactory.getLogger("");
	protected static final Logger LOGCOMM = LoggerFactory.getLogger("comm");

	protected String name;
	protected SocketAddress address;
	protected IoAcceptor acceptor;
	protected Class<T> clientClazz;
	protected Object clientContext;

	// 所连接的客户端
	// sessionID <--> Client 映射表
	protected Map<Long, T> clients = new ConcurrentHashMap<Long, T>();

	public String getName() { return name; }
	public void setName(String name) { this.name = name; }
	public IoAcceptor getAcceptor() { return acceptor; }
	public SocketAddress getAddress() { return address; }
	public Map<Long, T> getClients() { return clients; }
	public T getClient(IoSession session) { return clients.get(session.getId()); }
	public T getClient(Long sessionId) { return (sessionId == null) ? null : clients.get(sessionId); }

	// ANY
	public ServerBase(String name, SocketAddress address, Class<T> clientClazz, Object clientContext, IoAcceptor acceptor) {
		this.name = name;
		this.address = address;
		this.clientClazz = clientClazz;
		this.clientContext = clientContext;
		this.acceptor = acceptor;
	}

	public ServerBase(String name, InetSocketAddress address, Class<T> clientClazz, Object clientContext, boolean tcp) {
		this(name, address, clientClazz, clientContext, tcp ? (new NioSocketAcceptor(1)) : (new NioDatagramAcceptor()));
	}

	public ServerBase(String name, int port, Class<T> clientClazz, Object clientContext, boolean tcp) {
		this(name, new InetSocketAddress(port), clientClazz, clientContext, tcp ? (new NioSocketAcceptor(1)) : (new NioDatagramAcceptor()));
	}

	/**
	 * 开始服务
	 * @return
	 */
	public boolean start() {
		acceptor.setHandler(this);
		acceptor.setSessionDataStructureFactory(this);

		try {
	        // 绑定端口,启动服务，并开始处理远程客户端请求
			LOG.info("启动 {} 服务器({}) ...", name, address);
	        acceptor.bind(address);
		} catch (Exception e) {
			LOG.error("启动 {} 服务器失败！", name, e);
			return false;
		}

		LOG.info("启动 {} 服务器成功！", name);
        return true;
	}

	/**
	 * 停止服务
	 */
	public void stop() {
		// 关闭所有客户端连接
		for(T client: clients.values()) {
			client.close();
		}
		clients.clear();

		// 关闭监听
		if(acceptor != null) {
			LOG.info("正在关闭 {} 服务器 ...", name);
			acceptor.dispose();
			acceptor = null;
		}
		LOG.info("已关闭 {} 服务器！", name);
	}

	/**
	 * 为每个 Session 增加与 自己的关联
	 */
	@Override
	public IoSessionAttributeMap getAttributeMap(IoSession session) throws Exception {
		IoSessionAttributeMap map = super.getAttributeMap(session);
		map.setAttribute(session, "server", this);
		return map;
	}

	/**
	 * 断开客户端连接，同时删除客户端对象
	 */
	public void closeClient(IoSession session) {
		T client = clients.remove(session.getId());
		if(client != null) {
			client.close();
		}
	}

	/**
	 * IoHandler 事件处理
	 */
    @Override
    public void sessionCreated(IoSession session) {
		sessionCreated(session, clientContext);
    }

	/**
	 * IoHandler 事件处理
	 */
    public T sessionCreated(IoSession session, Object context) {
        //LOGCOMM.debug("Session created...");

		try {
			// 创建一个对应的客户端对象，与此session绑定
			T client = clientClazz.newInstance();
			client.onCreate(session, context);

			// 把客户端添加到客户端列表
			clients.put(session.getId(), client);

			LOG.debug("服务器 {} 接受了一个连接请求: {}", name, client);
			return client;
		} catch(Exception e) {
			// 关闭连接
			session.close(true);
			LOG.error("创建客户端连接失败!", e);
		};
		return null;
    }

    @Override
    public void sessionOpened(IoSession session) throws Exception {
        //LOGCOMM.debug("messageReceived: {}", message.toString());
    	if(getClient(session) == null) { return; }

        try {
            getClient(session).onOpened();
		} catch (Exception e) {
			LOGCOMM.error("调用 {}.onOpened() 失败！", getClient(session), e);
		}
    }

    @Override
    public void messageReceived(IoSession session, Object message) throws Exception {
        //LOGCOMM.debug("messageReceived: {}", message.toString());
    	if(getClient(session) == null) { return; }

    	try {
            getClient(session).onMessageReceived(message);
		} catch (Exception e) {
			LOGCOMM.error("调用 {}.onMessageReceived('{}') 失败！", getClient(session), message, e);
		}
    }

    @Override
    public void messageSent(IoSession session, Object message) throws Exception {
        //LOGCOMM.debug("Message sent...");
    	if(getClient(session) == null) { return; }

    	try {
            getClient(session).onMessageSent(message);
		} catch (Exception e) {
			LOGCOMM.error("调用 {}.onMessageSent('{}') 失败！", getClient(session), message, e);
		}
    }

    @Override
    public void sessionClosed(IoSession session) throws Exception {
        //LOGCOMM.debug("Session closed...");
    	if(getClient(session) == null) { return; }

    	try {
            getClient(session).onClosed();
		} catch (Exception e) {
			LOGCOMM.error("调用 {}.onClosed() 失败！", getClient(session), e);
		}
        closeClient(session);
    }

    @Override
    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
    	if(getClient(session) == null) { return; }

    	try {
            getClient(session).onIdle(status);
		} catch (Exception e) {
			LOGCOMM.error("调用 {}.onIdle({}) 失败！", getClient(session), status, e);
		}
    }

    @Override
    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
        LOGCOMM.error("exceptionCaught: {}", cause);
    	if(getClient(session) == null) { return; }

    	try {
            getClient(session).onExceptionCaught(cause);
		} catch (Exception e) {
			LOGCOMM.error("调用 {}.onExceptionCaught({}) 失败！", getClient(session), cause, e);
		}
        //closeClient(session);
    }
}
